﻿using MoneyWentWhere.DataModel.Utils;
using MoneyWentWhere.Utils.Storage;
using SimpleCsv;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MoneyWentWhere.DataModel.Storage
{
    class CsvFormat : FileFormat<MoneyDataSet>
    {
        const int BufferSize = 10 * 1024;

        const String Key_DefaultCurrency = "DefaultCurrency";

        const String DateFormat = "yyyy-MM-dd";
        const char TagDelim = ' ';
        const char TagQuote = '\'';

        public override MoneyDataSet Load(Stream inStream, string password = null)
        {

            var sr = new StreamReader(inStream);
            var sections = SplitSections(Csv.Read(sr, leaveOpen: true)).ToList();

            MoneyDataSet doc = new MoneyDataSet();

            // General data
            var general = sections[0].ToDictionary(x => x.Next(), x => x.Next());
            doc.DefaultCurrency = new Currency(general[Key_DefaultCurrency]);

            // Accounts
            foreach(var row in sections[1])
            {
                var guid = row.ParseNext<Guid>();
                var name = row.Next();
                var currency = new Currency(row.Next());

                Account ac = new Account(doc, name, guid) { Currency = currency };
                doc.Accounts.Add(ac);
            }

            // Transactions
            foreach(var row in sections[2])
            {
                var guid = row.ParseNext<Guid>();
                var account = (Account)doc.ObjectByGuid[row.ParseNext<Guid>()];
                var date = DateTime.ParseExact(row.Next(), DateFormat, CultureInfo.InvariantCulture);
                var amount = row.ParseNext<decimal>();
                var comment = row.Next();

                var category = row.Next();
                var tags = new TagSet(row.ParseNextList<List<String>>(TagDelim, TagQuote));
                var recoBalance = row.ParseNext<decimal?>();
                var transferToTxGuid = row.ParseNext<Guid?>();

                Transaction tx = new Transaction(guid, account, date, amount, recoBalance, transferToTxGuid);
                tx.Comment = comment;
                tx.Category = category;
                account.Transactions.Add(tx);
            }

            return doc;
        }

        public override void Save(MoneyDataSet doc, Stream outStream)
        {
            using (var w = new CsvWriter(new StreamWriter(outStream), leaveOpen: true))
            {
                WriteSection(w, "General", 
                    U.List("Key", "Value"),
                    U.List(
                        new CsvRow {"Application", "MoneyWentWhere"},
                        new CsvRow {"Version", "1.3"},
                        new CsvRow {Key_DefaultCurrency, doc.DefaultCurrency }
                    ));

                WriteSection(w, "Accounts",
                    U.List(
                        "Guid", 
                        "Name", 
                        "Currency"),
                    doc.Accounts.Select(ac => new CsvRow
                    {
                        ac.Guid,
                        ac.Name,
                        ac.Currency,
                    }));

                WriteSection(w, "Transactions",
                    U.List("Guid",
                           "AccountGuid",
                           "Date",
                           "Amount",
                           "Comment",

                           "Category",
                           "Tags",
                           "RecoBalance",
                           "TransferToTxGuid"),
                    doc.Accounts.SelectMany(ac => ac.Transactions)
                        .Select(tx => new CsvRow
                        {
                            tx.Guid,
                            tx.Account.Guid,
                            tx.Date.ToString(DateFormat, CultureInfo.InvariantCulture),
                            tx.Amount,
                            tx.Comment,
                            
                            tx.Category,
                            Csv.EncodeRow(tx.Tags, TagDelim, TagQuote),
                            tx.RecoBalance,
                            tx.TransferToTx == null ? "" : tx.TransferToTx.Guid.ToString()
                        }));
            }
        }


        void WriteSection(CsvWriter w, String header, IEnumerable<String> columns, IEnumerable<CsvRow> rows)
        {
            w.WriteRow("*** " + header);

            var cols = columns.ToArray();
            cols[0] = "// " + cols[0];
            w.WriteRow(cols);
            
            foreach (CsvRow row in rows)
            {
                w.WriteRow(row);
            }
            w.WriteRow();
        }

        IEnumerable<IEnumerable<CsvRow>> SplitSections(IEnumerable<CsvRow> allRows)
        {
            List<CsvRow> section = null;
            foreach(CsvRow row in allRows)
            {
                // Ignore comments
                var first = row.Count == 0 ? "" : row[0];

                // Skip empty lines and comments
                if (row.IsEmpty || first.StartsWith("//")) { continue; }

                if (first.StartsWith("*** "))
                {
                    // Return and end the section
                    if (section != null)
                    {
                        yield return section;
                        section = null;
                    }
                }
                else
                {
                    if (section == null) { section = new List<CsvRow>(); }

                    // Add row to section
                    section.Add(row);
                }
            }

            if (section != null) { yield return section; }
        }
    }
}
